首先环境自己准备,不会的话就参考http://mxblog.mxguanwang.cn/2741.html
文件:MainActivity.java
package com.mxguanwang.mxcar;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity extends AppCompatActivity {
private mxcarView mMxcarView = null;
private TcpClient tcp = new TcpClient();
long time_send = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setContentView(R.layout.activity_mxcar);
FrameLayout frame = (FrameLayout) findViewById(R.id.mxcar);
mMxcarView = new mxcarView(MainActivity.this);
mMxcarView.SetMainActivity(MainActivity.this);
mMxcarView.Init();
mMxcarView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
return mMxcarView.OnTouch(event);
}
});
frame.addView(mMxcarView);
time_send = System.currentTimeMillis();
tcp.SetMainActivity(this);
tcp.Run();
// 起定时器 定时更新延迟
new Timer().schedule(new TimerTask() {
@Override
public void run()
{
SendControl();
}
}, 0,100);
}
void SendControl()
{
if (mMxcarView.mIsConnect)
{
String strSend = String.format("%04d%04d",mMxcarView.sendx,mMxcarView.sendy);
tcp.Send(strSend.getBytes());
time_send = System.currentTimeMillis();
}
}
// 服务器连接
public void OnConnect()
{
mMxcarView.mIsConnect = true;
byte[] bSend = new byte[1];
bSend[0] = '2';
tcp.Send(bSend);
}
// 服务器断开
public void OnDisConnect()
{
mMxcarView.mIsConnect = false;
}
// 传入数据 和类型
public void OnRecv(byte[] pData)
{
String strRecv = new String(pData);
int time_car = Integer.parseInt(strRecv.substring(0,4));
int state_car = Integer.parseInt(strRecv.substring(4,8));
mMxcarView.time_car = time_car;
mMxcarView.state_car = state_car;
mMxcarView.time_input = (int) (System.currentTimeMillis() - time_send);
mMxcarView.invalidate();
}
}
文件:mxcarView.java
package com.mxguanwang.mxcar;
import static java.lang.Math.abs;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import java.util.HashMap;
import java.util.Map;
public class mxcarView extends View
{
public mxcarView(Context context) {
super(context);
}
// 双缓冲实现
private Bitmap mBitmap; //缓存绘制的内容
private Canvas mCanvas; //内存中创建的Canvas
// 手指圆形
private Paint paint_Show = new Paint();
// 外圈圆形
private Paint paint_Big = new Paint();
private Paint paint_bk = new Paint();
// 是否连接网络
public boolean mIsConnect = false;
int cx = 0;
int cy = 0;
float radius = 0;
// 小车的延迟
int time_car = 0;
// 小车的状态 0未连接 1已连接
int state_car = 0;
// 控制的延迟
int time_input = 0;
// 当前手指的位置,用来画圆形
private static final Point ptCur = new Point();
public int sendx = 0;
public int sendy = 0;
MainActivity mainActivity = null;
public void SetMainActivity(MainActivity activiy)
{
mainActivity = activiy;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
// 初始化bitmap
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
radius = mBitmap.getWidth()/2-100;
cx = (int) (radius + 100);
cy = (int) (mBitmap.getHeight() - radius - 80);
ptCur.x = cx;
ptCur.y = cy;
}
@SuppressLint("ResourceAsColor")
public void Init()
{
paint_Big.setTextSize(25);
paint_Big.setStyle(Paint.Style.STROKE);
paint_Big.setColor(Color.YELLOW);
paint_Big.setAntiAlias(true);
paint_Big.setStrokeWidth(5);
paint_Show.setTextSize(25);
paint_Show.setStyle(Paint.Style.FILL);
paint_Show.setColor(Color.GREEN);
paint_Show.setAntiAlias(true);
paint_bk.setColor(getResources().getColor(com.google.android.material.R.color.design_default_color_background));
paint_bk.setTextSize(25);
paint_bk.setStyle(Paint.Style.FILL);
}
// 绘制地图
void DrawMap()
{
mCanvas.drawRect(new Rect(0,0,mBitmap.getWidth(),mBitmap.getHeight()),paint_bk);
int posY = 50;
// 服务器连接状态
if (mIsConnect)
{
mCanvas.drawText("服务器状态:已连接",10,posY,paint_Show);
}
else
{
mCanvas.drawText("服务器状态:未连接",10,posY,paint_Show);
time_input = -1;
}
posY += 40;
// 小车连接状态
if (state_car == 1)
{
mCanvas.drawText("小车状态:已连接",10,posY,paint_Show);
}
else
{
mCanvas.drawText("小车状态:未连接",10,posY,paint_Show);
time_car = -1;
}
posY += 40;
// 服务器连接延迟
mCanvas.drawText(String.format("服务器延迟:%dms",time_input),10,posY,paint_Show);
posY += 40;
// 小车连接延迟
mCanvas.drawText(String.format("小车延迟:%dms",time_car),10,posY,paint_Show);
posY += 40;
// 速度
mCanvas.drawText(String.format("当前速度: %d-%d",sendx,sendy),10,posY,paint_Show);
mCanvas.drawCircle(cx,cy,radius,paint_Big);
mCanvas.drawCircle(ptCur.x,ptCur.y,100,paint_Show);
// Log.d("DrawMap", String.format("DrawMap: off(%d-%d)",ptCur.x,ptCur.y));
}
// 重写绘制,按照自己的方式绘制图像
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
DrawMap();
canvas.drawBitmap(mBitmap,0,0,null);
}
void CalcPoint(int x,int y)
{
float conX = 0;
float conY = 0;
conX = x - cx;
conY = y - cy;
conX = conX / radius;
conY = conY / radius;
if (conX < -1)
{
conX = -1;
}
if (conX > 1)
{
conX = 1;
}
if (conY < -1)
{
conY = -1;
}
if (conY > 1)
{
conY = 1;
}
sendx = (int) (1023*conX);
sendy = (int) (1023*conY);
if (sendx < 0)
{
sendx = 5000 - sendx;
}
if (sendy < 0)
{
sendy = 5000 - sendy;
}
Log.d("OnTouch", String.format("百分比: off(%.2f%%-%.2f%%) - send(%d,%d)",
conX*100,conY*100,sendx,sendy));
ptCur.x = x;
ptCur.y = y;
if (mainActivity != null)
{
mainActivity.SendControl();
}
invalidate();
}
public final boolean OnTouch(@NonNull MotionEvent event)
{
int x = (int)event.getX();
int y = (int)event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_UP:
{
x = cx;
y = cy;
}
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
{
CalcPoint(x,y);
break;
}
}
return true;
}
}
文件:TcpClient.java
package com.mxguanwang.mxcar;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.sql.Array;
import java.util.Date;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;
// 操作网络数据的类
public class TcpClient {
boolean Connected = false;
Socket socket = null; // 定义socket
private OutputStream outputStream = null; // 定义输出流(发送)
private InputStream inputStream = null; // 定义输入流(接收)
static String strIp = "你的IP";
static int nPort = 6668;// 你的端口
long lastTick = 0;
// 是否已经接收到回复了,没有回复就抛弃当前消息。
boolean bIsRecv = false;
MainActivity mainActivity = null;
// 是否已经连接
boolean bIsConnect = false;
// 保存需要发送的数据
LinkedList<byte[]> listSend = new LinkedList<byte[]>();
public void SetMainActivity(MainActivity activiy)
{
mainActivity = activiy;
}
void OnDisConnect()
{
if (!socket.isClosed())
{
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bIsConnect)
{
bIsConnect = false;
if (mainActivity != null)
{
mainActivity.OnDisConnect();
}
}
}
// 初始化加运行
public boolean Run()
{
Connect_Thread connect_Thread = new Connect_Thread();
connect_Thread.start();
Receive_Thread receive_Thread = new Receive_Thread();
receive_Thread.start();
Send_Thread send_Thread = new Send_Thread();
send_Thread.start();
return true;
}
// 发送数据
public boolean Send(byte[] pData)
{
if (socket == null || !socket.isConnected())
{
return false;
}
listSend.addLast(pData);
return true;
}
// 连接线程
class Connect_Thread extends Thread
{
// 重写run方法
public void run()
{
System.out.println("连接线程已经启动");
if (socket != null)
{
System.out.println("连接线程 socket != null");
return;
}
InetAddress ipAddress = null;
try {
ipAddress = InetAddress.getByName(strIp);
} catch (UnknownHostException e) {
e.printStackTrace();
}
SocketAddress addr = new InetSocketAddress(ipAddress,nPort);
socket = new Socket();
while (true)
{
try{ Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
try
{
if (bIsConnect)
{
continue;
}
else
{
if (inputStream != null)
{
inputStream.close();
}
if (outputStream != null)
{
outputStream.close();
}
}
// 被关闭后无法连接,重新new一个
if (socket.isClosed())
{
socket = new Socket();
}
socket.connect(addr,1000);
if (socket.isConnected())
{
socket.setSoTimeout(2000);
outputStream = socket.getOutputStream();
inputStream = socket.getInputStream();
bIsConnect = true;
lastTick = System.currentTimeMillis();
System.out.println("服务器连接成功");
mainActivity.OnConnect();
}
else
{
System.out.println("服务器连接失败");
}
}catch (Exception e)
{
if (e.getMessage().indexOf("timed out") == -1)
{
// TODO Auto-generated catch block
System.out.println("err :"+ e.getMessage());
e.printStackTrace();
OnDisConnect();
}
}
}
}
}
// 接收线程
class Receive_Thread extends Thread
{
public void run()// 重写run方法
{
System.out.println("接收线程已经启动");
final int HeadLen = 28;
// 专门用来接收数据头
final byte[] bufferHead = new byte[HeadLen];// 创建接收缓冲区
// 数据头接收的数据
int iRecvHeadLen = 0;
// 数据接收缓冲区
final byte[] bufferRecv = new byte[8*1024];
// 当前已经接收的数据
int iRecvLen = 0;
// 数据总长度,需要从head中读取
int iDataLen = 8;
// 缓冲区长度,暂时不考虑。后续再说。
int iBuffSize = 8*1024;
while (true)
{
try
{
if (!bIsConnect)
{
System.out.println("接收线程 socket未连接");
// 如果断开就重置并丢弃所有数据。
iRecvHeadLen = 0;
iRecvLen = 0;
iDataLen = 8;
try{ Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
continue;
}
// 按需读取,保证不会接收过多。
int iNeed = iDataLen - iRecvLen;
int iRecv = inputStream.read(bufferRecv,iRecvLen,iNeed);
if (iRecv <= 0)
{
continue;
}
iRecvLen += iRecv;
if (iRecvLen < iDataLen)
{
continue;
}
// 接收到正确的数据包则更新时间
// lastTick = System.currentTimeMillis();
// 这里肯定已经接收完毕,并且数据量刚刚好
if (mainActivity != null)
{
mainActivity.OnRecv(bufferRecv);
}
// 对数据进行初始化
iRecvHeadLen = 0;
iRecvLen = 0;
iDataLen = 8;
}
catch (IOException e)
{
// TODO Auto-generated catch block
// System.out.println("recv err :"+ e.getMessage());
// 除超时之外的直接关闭
if (e.getMessage().indexOf("timed out") == -1)
{
e.printStackTrace();
OnDisConnect();
continue;
}
}
}
}
}
// 发送线程,安卓不允许主线程发送数据
class Send_Thread extends Thread
{
public void run()// 重写run方法
{
System.out.println("发送线程已经启动");
while (true)
{
try
{
if (listSend.isEmpty())
{
try{ Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
continue;
}
if (!bIsConnect)
{
System.out.println("发送线程 socket未连接");
// 如果断开就重置并丢弃所有数据。
try{ Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
continue;
}
outputStream.write(listSend.getFirst());
listSend.removeFirst();
} catch (IOException e)
{
// TODO Auto-generated catch block
System.out.println("send err :"+ e.getMessage());
e.printStackTrace();
// 发送出现异常就直接关闭并重连
OnDisConnect();
}
}
}
}
}
注意事项:
1.我不懂安卓,也不懂java,代码都是东拼西凑出来的。
2.好像有bug,出现过多次软件闪退的问题,用来测试没问题,但是尽量不要上路,如果找到bug的话请告诉我,多谢了。
3.如果上面代码不全,那没关系,我还提供了整个项目的包。
4.截图如下:

如果失效了可以用百度网盘的https://pan.baidu.com/link/zhihu/7ph3zauRhriTShl0R3XidxZ2RtVLd0bQQ5RH==
代码请改名为zip文件再解压。
Comments | NOTHING